/**
 * This file is released under the GNU General Public License.
 * Refer to the COPYING file distributed with this package.
 *
 * Copyright (c) 2008-2009 WURFL-Pro srl
 */
package net.sourceforge.wurfl.core.resource;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.lang.text.StrBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class is responsible applying patches to devices Map.
 * 
 * <p>
 * This class do not verify consistency, it apply only the patches to devices.
 * It is intended to use by DefaultWURFLModel.
 * </p>
 * 
 * @author WURFL-PRO SRL, Rome, Italy
 * @version $Id: WURFLPatchingManager.java 1045 2009-03-09 15:52:53Z filippo.deluca $
 */
public class WURFLPatchingManager {

	/** Log */
	private static final Log log = LogFactory
			.getLog(WURFLPatchingManager.class);

	/**
	 * Patch devices by patching devices.
	 * 
	 * @param devices
	 *            The devices to patch.
	 * @param patchingDevices
	 *            The patching devices.
	 * 
	 * @return patched ModelDevices.
	 */
	public static ModelDevices patchDevices(ModelDevices devices,
			ModelDevices patchingDevices) {

		// Create patchedDevices and add all original devices
		ModelDevices patchedDevices = new ModelDevices(devices);

		Iterator/* ModelDevice */patchingDevicesIterator = patchingDevices
				.iterator();
		while (patchingDevicesIterator.hasNext()) {

			ModelDevice patchedDevice = null;

			ModelDevice patchingDevice = (ModelDevice) patchingDevicesIterator
					.next();

			// Device exists
			if (devices.containsId(patchingDevice.getID())) {

				if (log.isDebugEnabled()) {
					log.debug("Patching device: " + patchingDevice.getID());
				}

				ModelDevice existDevice = devices.getById(patchingDevice
						.getID());

				// Return a new instance
				patchedDevice = patchDevice(existDevice, patchingDevice);

				// Removing existing instance
				patchedDevices.remove(existDevice);

			}
			// Device does not exist
			else {
				if (log.isDebugEnabled()) {
					log.debug("Adding device: " + patchingDevice.getID());
				}

				patchedDevice = patchingDevice;
			}

			assert patchedDevice != null : "patchedDevice is null";
			patchedDevices.add(patchedDevice);

		}

		return patchedDevices;
	}

	/**
	 * Patches single ModelDevice with patching device. It return a new instance
	 * of ModelDevice.
	 * 
	 * @param device
	 *            The ModelDevice to patch.
	 * @param patchingDevice
	 *            The patching ModelDevice.
	 * @return Patched ModelDevice. It is new instance.
	 */
	private static ModelDevice patchDevice(ModelDevice device,
			ModelDevice patchingDevice) {

		// Returning device
		ModelDevice patchedDevice = null;

		// patch capabilities
		Map patchedCapabilities = new HashMap(device.getCapabilities());
		patchedCapabilities.putAll(patchingDevice.getCapabilities());

		if (log.isTraceEnabled()) {
			StrBuilder logBuilder = new StrBuilder();
			logBuilder.append("Merged ").append(device.getID()).append(
					" capabilities: ").append(device.getCapabilities()).append(
					" + ").append(patchingDevice.getCapabilities()).append(
					" = ").append(patchedCapabilities);

			log.trace(logBuilder.toString());
		}

		// patch capabilities structure
		Map deviceCapabilitiesByGroup = device.getGroupsByCapability();
		Map patchingCapabilitiesByGroup = patchingDevice
				.getGroupsByCapability();
		Map patchedCapabilitiesByGroup = new HashMap();
		patchedCapabilitiesByGroup.putAll(deviceCapabilitiesByGroup);
		patchedCapabilitiesByGroup.putAll(patchingCapabilitiesByGroup);

		if (log.isTraceEnabled()) {

			StrBuilder logBuilder = new StrBuilder();
			logBuilder.append("Merged ").append(device.getID()).append(
					" capabilitiesByGroup: ").append(deviceCapabilitiesByGroup)
					.append(" + ").append(patchingCapabilitiesByGroup).append(
							" = ").append(patchedCapabilitiesByGroup);

			log.trace(logBuilder.toString());

		}

		if (!patchingDevice.getUserAgent().equals(device.getUserAgent())) {
			UserAgentOverrideException e = new UserAgentOverrideException(
					patchingDevice, patchingDevice.getUserAgent(), device
							.getUserAgent());

			// It is not possible to override user-agent for security reasons.
			throw new UserAgentOverrideException(device, patchingDevice
					.getUserAgent(), device.getUserAgent());
		}

		patchedDevice = new ModelDevice.Builder(device.getID(), device
				.getUserAgent(), patchingDevice.getFallBack())
				.setActualDeviceRoot(patchingDevice.isActualDeviceRoot())
				.setCapabilitiesByGroup(patchedCapabilitiesByGroup)
				.setCapabilities(patchedCapabilities).build();

		return patchedDevice;
	}

}
